<?php
require_once(dirname(__file__) . '/../etc/passdoc.php');
/**
 * This is a database connector made for the IXS2 system used by IAESTE to
 * provide an OO database connector. It uses functions used elsewhere in IXS2
 * but provides additional functionality to ensure a quick and efficient way
 * of connecting and using the postgreSQL database.
 *
 * @author Eirik Eggesbø Ottesen
 * @version 1.1
 * @package Core
 */
class PGSQLConnector {
    private $handle = null;
    private static $queryCount = 0;
    private static $linkCount = 0;
    private static $handleArray = Array();

    /**
     * PGSQLConnector::__construct()
     *
     * This is the default constructor of the connector for the pgsql backend
     * of the IXS2 system. The constuctor sets up an object that includes a
     * connection handle to the database set by the configuration. The user
     * used by the connection is specified by the $user parameter.
     *
     * Note: The class ensures that only one connection to a database with
     * the same configuration is made, even though several instances of the
     * object is created.
     *
     * @global array $config
     * @param String $user
     */
    public function __construct($user) {

        // Load everything else from global config variables
        //TODO Stop relying on global variables!
        global $config;
        $passwd = getPass($user);

        // Handle any errors
        if(empty($passwd)) {
            throw new Exception('Internal error, application terminated.');
        }

        $dbname = $config['db_name'];
        $port = $config['db_port'];
        $host = $config['db_host'];

        // Check exisisting handles for a connection
        $hash = self::crc16($host . $port . $dbname . $user . $passwd);
        if(isset(self::$handleArray[$hash])) {
            $this->handle = self::$handleArray[$hash];
        }

        // Link with same properties does not exist, create new
        else {
            $this->handle = pg_connect("host=$host port=$port dbname=$dbname user=$user password=$passwd");
        }

        // Check for errors and throw exception if one has occured
        if(!$this->handle) {
            throw new Exception('Failed to connect to database.');
        }

        // Insert this handle into handle array and increase number of links
        self::$handleArray[$hash] = $this->handle;
        self::$linkCount++;
    }

    /**
     * PGSQLConnector::query()
     *
     * Simple database query method. This method sends a query to the
     * database with the handle created by the constructor of this object.
     * If the query cannot be preformed an exception is thrown with the error
     * message from the database server. If no error occurs the result of the
     * query is returned.
     *
     * @global array $debug
     * @param String $query
     * @return resource
     */
    public function query($query) {

        // Echo debug messages if option is set
        global $debug; //TODO Stop relying on globals...
        if($debug['SQL']) showSQL($query);

        // Attempt to query the database using innput
        $rawResult = pg_query($this->handle, $query);
        self::$queryCount++;

        // Check for errors
        if(!$rawResult) {
            throw new Exception('Error in query. PostgreSQL said: ' . pg_last_error());
        }

        return $rawResult;
    }

    /**
     * PGSQLConnector::query2array()
     *
     * This is a wrapper for the query method. It uses the objects query method
     * but wraps the raw SQL result in an assosiative array for easier access
     * to the data.
     *
     * NOTE: It also clears the raw results from memory before returning
     *
     * @param String $query
     * @return array
     */
    public function query2array($query) {

        // Use own query function to query database
        $rawResult = $this->query($query);

        // If exception occurs forward, else continue building array
        $array = array();
        while($row = pg_fetch_assoc($rawResult)) {
            $array[] = $row;
        }

        // Clear raw result to free memory
        pg_free_result($rawResult);

        return $array;
    }

    /**
     * The method is a wrapper for the query2array class, but instead of
     * returning the query result as a php array it converts the array to a
     * JSON object. This is usefull when data is transferred between
     * applications.
     *
     * @param String $query
     * @param Int $queryType
     * @return String
     */
    public function query2json($query, $queryType=1) {

        // Use own query function to query database
        if($queryType == 1) {
            $rawResult = $this->query2array($query);
        }
        // query type specified, only 1 row should be fetched
        else {
            $rawResult = $this->queryUnique($query);
        }
        
        return json_encode($rawResult);
    }

    /**
     * PGSQLConnector::queryUnique()
     *
     * This is a wrapper for the query method. It uses the objects query method
     * but adds additional security checks to ensure that only one row is
     * returned from the database.
     *
     * @param String $query
     * @return array
     */
    public function queryUnique($query) {

        // Use internal method to query database
        $rawResult = $this->query($query);

        // Check to see that exacly 1 row is returned
        if(pg_num_rows($rawResult) > 1) {
            throw new Exception('SQL result contains more than one row.');
        }
        if(pg_num_rows($rawResult) < 1) {
            throw new Exception('SQL result was empty.');
        }

        // Only one row returned, return the row as an assosiative array
        return pg_fetch_assoc($rawResult);
    }

    public function transaction(array $queries) {

        // Begin transaction
        $this->query('BEGIN;');

        // Pass each query in the collection on to the database
        try {
            foreach($queries as $query) {
                $this->query($query);
            }
        }
        catch(Exception $e) {
            $this->query('ROLLBACK;');
            throw $e;
        }

        // End query
        $this->query('COMMIT;');
    }

    /**
     * This function provides a wrapper for the native php escape string function
     * it uses its own stored connection for the handle needed by the escape
     * string function.
     * 
     * @param String $string
     * @return String 
     */
    public function escapeString($string) {
        return pg_escape_string($this->handle, $string);
    }

   /**
   * PGSQLConnector::crc16()
   *
   * This is a hash function that has a smaller result than the most commonly
   * used ones. It is not a security hashing function but is usefull for quickly
   * checking the integrety of non-critical data, or to recognise unique
   * values in small to medium large datasets.
   * Credit goes to http://www.ccsinfo.com/forum/viewtopic.php?t=24977
   *
   * @param any $data
   * @return numeric hash
    * @todo Move this to another, more appropriate place in IXS2
   */
   private static function crc16($data) {
       $crc = 0xFFFF;
       for ($i = 0; $i < strlen($data); $i++) {
           $x = (($crc >> 8) ^ ord($data[$i])) & 0xFF;
           $x ^= $x >> 4;
           $crc = (($crc << 8) ^ ($x << 12) ^ ($x << 5) ^ $x) & 0xFFFF;
       }
       return $crc;
   }
}
?>
